昨天的文章帶出了透過提供 NG_VALIDATOR 的方式注入 validator 到 FormControlName 中,阿你有想過 FormControlName 是怎麼對應出 FormControl 實體的嗎?
第一個線索是 HTML 上的 [formGroup]="formgroup"
,從這邊可以得知 Angular 是透過這個 directive 與我們在 TypeScript 所建好的 FormGroup 實體建立起關聯。
需要關注的 FormGroupDirective 的實作(完整內容)如下:
@Directive({
selector: '[formGroup]',
providers: [formDirectiveProvider],
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
exportAs: 'ngForm'
})
export class FormGroupDirective extends ControlContainer implements Form, OnChanges {
// ... 略
/**
* @description
* Tracks the `FormGroup` bound to this directive.
*/
@Input('formGroup') form: FormGroup = null!;
// ... 略
}
OK,現階段只需要知道 FormGroup 的物件有傳到 FormGroupDirective 就好。
接著就可以來看一下 FormControlName 的實作:
@Directive({selector: '[formControlName]', providers: [controlNameBinding]})
export class FormControlName extends NgControl implements OnChanges, OnDestroy {
private _added = false;
/**
* Internal reference to the view model value.
* @internal
*/
viewModel: any;
/**
* @description
* Tracks the `FormControl` instance bound to the directive.
*/
// TODO(issue/24571): remove '!'.
readonly control!: FormControl;
/**
* @description
* Tracks the name of the `FormControl` bound to the directive. The name corresponds
* to a key in the parent `FormGroup` or `FormArray`.
* Accepts a name as a string or a number.
* The name in the form of a string is useful for individual forms,
* while the numerical form allows for form controls to be bound
* to indices when iterating over controls in a `FormArray`.
*/
// TODO(issue/24571): remove '!'.
@Input('formControlName') name!: string|number|null;
// ... 下接 Block 2
}
↑ Block 1
與 FormGroupDirective 一樣,name 會直接被傳入到這個 directive 內,接下來要去哪裡爬我想應該不用我多說,直接往 ngOnChanges
這個方法去就對了:
// ... 略
/** @nodoc */
ngOnChanges(changes: SimpleChanges) {
if (!this._added) this._setUpControl();
if (isPropertyUpdated(changes, this.viewModel)) {
_ngModelWarning('formControlName', FormControlName, this, this._ngModelWarningConfig);
this.viewModel = this.model;
this.formDirective.updateModel(this, this.model);
}
}
// ... 略
private _checkParentType(): void {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (!(this._parent instanceof FormGroupName) &&
this._parent instanceof AbstractFormGroupDirective) {
ReactiveErrors.ngModelGroupException();
} else if (
!(this._parent instanceof FormGroupName) &&
!(this._parent instanceof FormGroupDirective) &&
!(this._parent instanceof FormArrayName)) {
ReactiveErrors.controlParentException();
}
}
}
private _setUpControl() {
this._checkParentType();
(this as {control: FormControl}).control = this.formDirective.addControl(this);
if (this.control.disabled && this.valueAccessor!.setDisabledState) {
this.valueAccessor!.setDisabledState!(true);
}
this._added = true;
}
↑ Block 2
因為是 @Input 屬性的變更,直接進到 ngOnChanges 方法也是合情合理,只要 _added
這個變數是 false
的狀態,就會呼叫 _setUpControl 方法。
呼叫 _setUpControl 方法後,首先會先透過呼叫 _checkParentType 這個方法,檢查有沒有 parent,parent 的型別是不是 FormGroup 或是 FormArray,不是的話就拋錯誤出來。
如果沒有錯誤,就可以從 parent 呼叫 addControl 方法,傳入 FormControlName directive,並取回在 parent 物件(FormGroup)定義好的 FormControl 類別物件!
addControl(dir: FormControlName): FormControl {
const ctrl: any = this.form.get(dir.path);
setUpControl(ctrl, dir);
ctrl.updateValueAndValidity({emitEvent: false});
this.directives.push(dir);
return ctrl;
}
↑ FormGroupDirective 的 addControl 方法實作內容
以上就是 Angular 從 FormControlName 這一個 directive 從 FormGroupDirective 找出對應的 FormControl 的旅途!讚嘆 DI、讚嘆 Directive!
至於 FormControl 跟 HTML input element 的 value 怎麼互動的,又可以是另一篇文章了呢。
以下按照入團順序列出我們團隊夥伴的系列文章!